// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: xguo

#include "coin2/strategy/order_executor/cancel_executor.h"

#include <algorithm>

namespace coin2::strategy::order_executor {

namespace {
using OrderContext = CancelExecutor::OrderContext;
bool compare_order_price(const OrderContext* a, const OrderContext* b) {
  return a->order_price() < b->order_price();
}
int64_t cooldown_order_id(int64_t order_id, bool has_exchange_id) {
  if (has_exchange_id) {
    return order_id;
  } else {
    return -order_id;
  }
}
}  // namespace

void CancelExecutor::Clear() {
  cancel_buy_list_.clear();
  cancel_sell_list_.clear();
}

void CancelExecutor::RemoveDuplicates() {}

void CancelExecutor::CancelBuyWorkingOrders(int64_t timestamp, int64_t exclude_order_id) {
  for (auto* order : wo_->GetBuyWorkingOrders()) {
    auto order_id = order->proc_order_id();
    auto cooldown_id = cooldown_order_id(order_id, order->has_exchange_order_id());
    if (order_id != exclude_order_id && !cooldown_limiter_->InCoolDown(timestamp, cooldown_id)) {
      cancel_buy_list_.insert(order_id);
      cooldown_limiter_->Update(timestamp, cooldown_id);
    }
  }
}

void CancelExecutor::CancelSellWorkingOrders(int64_t timestamp, int64_t exclude_order_id) {
  for (auto* order : wo_->GetSellWorkingOrders()) {
    auto order_id = order->proc_order_id();
    auto cooldown_id = cooldown_order_id(order_id, order->has_exchange_order_id());
    if (order_id != exclude_order_id && !cooldown_limiter_->InCoolDown(timestamp, cooldown_id)) {
      cancel_sell_list_.insert(order_id);
      cooldown_limiter_->Update(timestamp, cooldown_id);
    }
  }
}

void CancelExecutor::CancelBuyWithRisk(
    int64_t timestamp,
    double pos,
    double max_pos,
    double max_working_qty,
    int64_t exclude_order_id) {
  double qty = 0;
  double potential_pos = pos;
  for (auto* order : wo_->GetBuyOrders()) {
    const double order_qty = order->working_qty();
    qty += order_qty;
    potential_pos += order_qty;
    const auto order_id = order->proc_order_id();
    if (order_id == exclude_order_id) {
      continue;
    }

    if (!order->has_exchange_order_id() && need_exchange_id_) {
      continue;
    }

    auto cooldown_id = cooldown_order_id(order_id, order->has_exchange_order_id());
    if (cooldown_limiter_->InCoolDown(timestamp, cooldown_id)) {
      continue;
    }

    CHECK_GE(qty, 0.0) << qty;
    if (potential_pos - max_pos > 1e-9 || qty - max_working_qty > 1e-9) {
      cancel_buy_list_.insert(order_id);
      DLOG(INFO)
          << fmt::format("pp{} mp {} qty{} mwq {}", potential_pos, max_pos, qty, max_working_qty);
      cooldown_limiter_->Update(timestamp, cooldown_id);
    }
  }
}

void CancelExecutor::CancelSellWithRisk(
    int64_t timestamp,
    double pos,
    double min_pos,
    double max_working_qty,
    int64_t exclude_order_id) {
  double qty = 0;
  double potential_pos = pos;
  for (auto* order : wo_->GetSellOrders()) {
    const double order_qty = order->working_qty();
    qty += order_qty;
    potential_pos -= order_qty;
    const auto order_id = order->proc_order_id();
    if (order_id == exclude_order_id) {
      continue;
    }

    if (!order->has_exchange_order_id() && need_exchange_id_) {
      continue;
    }

    auto cooldown_id = cooldown_order_id(order_id, order->has_exchange_order_id());
    if (cooldown_limiter_->InCoolDown(timestamp, cooldown_id)) {
      continue;
    }

    CHECK_GE(qty, 0.0) << qty;
    if (potential_pos - min_pos < -1e-9 || qty - max_working_qty > 1e-9) {
      cancel_sell_list_.insert(order_id);
      DLOG(INFO)
          << fmt::format("pp{} mp {} qty{} mwq {}", potential_pos, min_pos, qty, max_working_qty);
      cooldown_limiter_->Update(timestamp, cooldown_id);
    }
  }
}

void CancelExecutor::CancelBuyByPrice(
    int64_t timestamp,
    double pull_price,
    double push_price,
    int64_t exclude_order_id) {
  // iterate one by one and cancel
  for (auto* order : wo_->GetBuyOrders()) {
    const auto order_id = order->proc_order_id();
    if (order_id == exclude_order_id) {
      continue;
    }
    if (!order->has_exchange_order_id() && need_exchange_id_) {
      continue;
    }
    auto cooldown_id = cooldown_order_id(order_id, order->has_exchange_order_id());
    if (cooldown_limiter_->InCoolDown(timestamp, cooldown_id)) {
      continue;
    }
    double price = order->order_price();
    if ((pull_price > 0. && price >= pull_price) || (push_price > 0. && price <= push_price)) {
      cancel_buy_list_.insert(order_id);
      DLOG(INFO) << fmt::format("pp{} price {} {} ", pull_price, price, push_price);
      cooldown_limiter_->Update(timestamp, cooldown_id);
    }
  }
}

void CancelExecutor::CancelSellByPrice(
    int64_t timestamp,
    double pull_price,
    double push_price,
    int64_t exclude_order_id) {
  // iterate one by one and cancel
  for (auto* order : wo_->GetSellOrders()) {
    const auto order_id = order->proc_order_id();
    if (order_id == exclude_order_id) {
      continue;
    }
    if (!order->has_exchange_order_id() && need_exchange_id_) {
      continue;
    }
    auto cooldown_id = cooldown_order_id(order_id, order->has_exchange_order_id());
    if (cooldown_limiter_->InCoolDown(timestamp, cooldown_id)) {
      continue;
    }
    double price = order->order_price();
    if ((pull_price > 0. && price <= pull_price) || (push_price > 0. && price >= push_price)) {
      cancel_sell_list_.insert(order_id);
      DLOG(INFO) << fmt::format("pp{} price {} {} ", pull_price, price, push_price);
      cooldown_limiter_->Update(timestamp, cooldown_id);
    }
  }
}

void CancelExecutor::CancelOldOrders(int64_t timestamp, int64_t timestamp_min) {
  for (auto* order : wo_->GetBuyOrders()) {
    auto order_id = order->proc_order_id();
    int64_t order_ts = order->order_created_time();
    if (!order->has_exchange_order_id() && need_exchange_id_) {
      continue;
    }
    auto cooldown_id = cooldown_order_id(order_id, order->has_exchange_order_id());
    if (order_ts <= timestamp_min && !cooldown_limiter_->InCoolDown(timestamp, cooldown_id)) {
      cancel_buy_list_.insert(order_id);
      DLOG(INFO) << fmt::format("old {} {} ", order_ts, timestamp_min);
      cooldown_limiter_->Update(timestamp, cooldown_id);
    }
  }

  for (auto* order : wo_->GetSellOrders()) {
    auto order_id = order->proc_order_id();
    int64_t order_ts = order->order_created_time();
    if (!order->has_exchange_order_id() && need_exchange_id_) {
      continue;
    }
    auto cooldown_id = cooldown_order_id(order_id, order->has_exchange_order_id());
    if (order_ts <= timestamp_min && !cooldown_limiter_->InCoolDown(timestamp, cooldown_id)) {
      cancel_sell_list_.insert(order_id);
      cooldown_limiter_->Update(timestamp, cooldown_id);
    }
  }
}

void CancelExecutor::CancelOldOrdersExceptTag(
    int64_t timestamp,
    int64_t timestamp_min,
    std::string tag) {
  for (auto* order : wo_->GetBuyOrders()) {
    auto order_id = order->proc_order_id();
    int64_t order_ts = order->order_created_time();
    const std::string order_tag = order->tag();
    if (!order->has_exchange_order_id() && need_exchange_id_) {
      continue;
    }
    auto cooldown_id = cooldown_order_id(order_id, order->has_exchange_order_id());
    if (tag != order_tag && order_ts <= timestamp_min &&
        !cooldown_limiter_->InCoolDown(timestamp, cooldown_id)) {
      cancel_buy_list_.insert(order_id);
      DLOG(INFO) << fmt::format("old {} {} ", order_ts, timestamp_min);
      cooldown_limiter_->Update(timestamp, cooldown_id);
    }
  }

  for (auto* order : wo_->GetSellOrders()) {
    auto order_id = order->proc_order_id();
    int64_t order_ts = order->order_created_time();
    const std::string order_tag = order->tag();
    if (!order->has_exchange_order_id() && need_exchange_id_) {
      continue;
    }
    auto cooldown_id = cooldown_order_id(order_id, order->has_exchange_order_id());
    if (tag != order_tag && order_ts <= timestamp_min &&
        !cooldown_limiter_->InCoolDown(timestamp, cooldown_id)) {
      cancel_sell_list_.insert(order_id);
      cooldown_limiter_->Update(timestamp, cooldown_id);
    }
  }
}

void CancelExecutor::CancelSmallBuyOrders(int64_t timestamp, double qty) {
  for (const OrderContext* order : wo_->GetBuyOrders()) {
    const auto order_id = order->proc_order_id();
    if (!order->has_exchange_order_id() && need_exchange_id_) {
      continue;
    }
    auto cooldown_id = cooldown_order_id(order_id, order->has_exchange_order_id());
    if (cooldown_limiter_->InCoolDown(timestamp, cooldown_id)) {
      continue;
    }
    double order_qty = order->working_qty();
    if (order_qty < qty) {
      cancel_buy_list_.insert(order_id);
      DLOG(INFO) << fmt::format("small B order qty {} {} ", order_qty, qty);
      cooldown_limiter_->Update(timestamp, cooldown_id);
    }
  }
}

void CancelExecutor::CancelSmallSellOrders(int64_t timestamp, double qty) {
  for (const OrderContext* order : wo_->GetSellOrders()) {
    const auto order_id = order->proc_order_id();
    if (!order->has_exchange_order_id() && need_exchange_id_) {
      continue;
    }
    auto cooldown_id = cooldown_order_id(order_id, order->has_exchange_order_id());
    if (cooldown_limiter_->InCoolDown(timestamp, cooldown_id)) {
      continue;
    }
    double order_qty = order->working_qty();
    if (order_qty < qty) {
      cancel_buy_list_.insert(order_id);
      DLOG(INFO) << fmt::format("small S order qty {} {} ", order_qty, qty);
      cooldown_limiter_->Update(timestamp, cooldown_id);
    }
  }
}

const OrderContext* CancelExecutor::ExtractBuy() {
  if (wo_->GetBuyWorkingOrders().empty()) {
    return nullptr;
  }

  // Choose one with the biggest price
  auto order_list = wo_->GetBuyWorkingOrders();
  auto* chosen = *std::max_element(order_list.begin(), order_list.end(), compare_order_price);

  int64_t order_id = chosen->proc_order_id();
  if (std::find(cancel_buy_list_.begin(), cancel_buy_list_.end(), order_id) !=
      cancel_buy_list_.end()) {
    cancel_buy_list_.erase(order_id);
  }
  return chosen;
}

const OrderContext* CancelExecutor::ExtractSell() {
  if (wo_->GetSellWorkingOrders().empty()) {
    return nullptr;
  }

  // Choose one with the smallest price
  auto order_list = wo_->GetSellWorkingOrders();
  auto* chosen = *std::min_element(order_list.begin(), order_list.end(), compare_order_price);
  int64_t order_id = chosen->proc_order_id();
  if (std::find(cancel_sell_list_.begin(), cancel_sell_list_.end(), order_id) !=
      cancel_sell_list_.end()) {
    cancel_sell_list_.erase(order_id);
  }
  return chosen;
}

void CancelExecutor::CancelOutsideTimeRangeOrders(
    int64_t timestamp,
    int64_t timestamp_from,
    int64_t timestamp_to) {
  for (auto* order : wo_->GetBuyOrders()) {
    auto order_id = order->proc_order_id();
    auto order_ts = order->order_created_time();
    bool tin = order_ts >= timestamp_from && order_ts <= timestamp_to;
    if (!order->has_exchange_order_id() && need_exchange_id_) {
      continue;
    }
    auto cooldown_id = cooldown_order_id(order_id, order->has_exchange_order_id());
    if (!tin && !cooldown_limiter_->InCoolDown(timestamp, cooldown_id)) {
      cancel_buy_list_.insert(order_id);
      cooldown_limiter_->Update(timestamp, cooldown_id);
    }
  }

  for (auto* order : wo_->GetSellOrders()) {
    auto order_id = order->proc_order_id();
    auto order_ts = order->order_created_time();
    bool tin = order_ts >= timestamp_from && order_ts <= timestamp_to;
    if (!order->has_exchange_order_id() && need_exchange_id_) {
      continue;
    }
    auto cooldown_id = cooldown_order_id(order_id, order->has_exchange_order_id());
    if (!tin && !cooldown_limiter_->InCoolDown(timestamp, cooldown_id)) {
      cancel_sell_list_.insert(order_id);
      cooldown_limiter_->Update(timestamp, cooldown_id);
    }
  }
}

}  // namespace coin2::strategy::order_executor
